X680x0アセンブラ講座 #$13 《アセンブラを使いこなすテクニック》 鎌田 誠
 今回は、アセンブラのプログラム書くときに使える小技をいろいろ紹介します。 これらのテクニックを使いこなして、効率のよいプログラミングを目指しましょ う。  ここでは HAS060.X(最新版は月刊電脳倶楽部 140 号に収録)を使う場合につ いて説明します。AS.X や HAS.X では使えない機能も含まれているので注意して 下さい。また、例を挙げるだけで細かい文法の説明を省略しているところがあり ます。是非、HAS060.X に添付されているマニュアルなども参照して下さい。
注釈の書き方
 プログラムの中に注釈を書くときは、'*' または ';' で書き始めます。どち らを使っても構いませんが、1 つのプログラムの中では統一しておきましょう。  '*' はオペランドの先頭にも(ロケーションカウンタを参照するときに)書く ことができるので、どちらかというと注釈の開始には ';' を使うことをお勧め します。私も ';' を使っています。 ●注釈の書き方のパターン  行頭に '*' または ';' を書くと、その右側は行末まで注釈と見なされて無視 されます。 ┌──────────────────────────── │;注釈  行頭に空白があっても構いません。 ┌──────────────────────────── │ ;注釈  ラベルの後ろに空白を入れて書くこともできます。 ┌──────────────────────────── │ラベル: ;注釈  命令や疑似命令あるいはマクロの後ろにも注釈を書くことができます。このと きは、空白を入れてから、なるべく ';' で開始するようにして下さい。 ┌──────────────────────────── │ 命令 ;注釈 │ラベル: 命令 ;注釈  オペランドの後に空白を入れて書くこともできます。オペランドの後に空白が あるときはそれ以降は '*' や ';' がなくても無視されますが、注釈は '*' ま たは ';' で書き始めるように心がけて下さい。 ┌──────────────────────────── │ 命令 オペランド ;注釈 │ラベル: 命令 オペランド ;注釈
':'(コロン)の使い方
 一般的に、行頭のラベルの末尾には ':' を付ける習慣があります。 ┌──────────────────────────── │foo: ~ という具合です。ここで、ラベルと ':' の間を空けてはいけません。なお、こ の書き方では命令や疑似命令あるいはマクロと同名のラベルを定義することがで きます。  行頭から始まっていれば(ラベルの左側に空白がなければ)':' がなくてもラ ベルと見なされます。 ┌──────────────────────────── │foo ~ でもよいわけです。ただし、この方法で命令や疑似命令あるいはマクロなどと同 名のラベルを定義するときは、同じ行の右側に命令や疑似命令あるいはマクロを 書くことで行頭の単語が命令などでないことを明確にする必要があります。  逆に、行頭から始まっていなくても(ラベルの左側に空白があっても)':' が 付いていればラベルと見なされます。 ┌──────────────────────────── │ char:=$61 │ .dc.b char これは char というシンボルに疑似命令 '='(疑似命令 set の別名)で $61 と いう値を与えています。2 行目の .dc.b で埋め込まれる値は $61(文字で言う と 'a')です。  HAS060.X に添付されている HANOI.S や K_MACRO.MAC が、行頭に空白を入れ て ':' を付けてラベルを定義する記法を使っているので、参考にして下さい。  なお、行頭のシンボルに定数を与える equ、set、=(set の別名)、reg、 fequ、fset などでは、':' を付けないほうが一般的です。
ローカルラベルの使い方
 多くの細かい分岐やループを記述するのに、いちいちジャンプ先のラベルを考 えるのは面倒です。ラベルを幾つも書いているとだんだんハナモゲラになってし まい、どこでどのラベルを参照しているのかがひと目でわからなくなってしまう こともあります。  そこで、HAS.X や HAS060.X では名無しのローカルラベルが使えるようになっ ています。名無しですが、ハナモゲラのラベルと比べればすぐ近くのジャンプ先 がわかりやすく、便利なものです。  ローカルラベルと言っても、スコープ(ラベルを参照できる範囲)に明確な制 限があるわけではありません。「同種の名無しローカルラベルが複数定義されて いるときは、前後どちらかの指定された方向で一番近くにあるものを参照する」 という単純な決まりがあるだけです。  名無しのローカルラベルをあまり遠いところから参照すると可読性の悪いソー スになってしまうので注意しましょう。  なお、ここで説明するローカルラベルは、マクロ内ローカルラベル(後述)と は関係ありません。 ● '@@'  ローカルラベル '@@' は、直前の '@f' または直後の '@b' から参照できます。 ┌──────────────────────────── │ bra @f ────┐ │@@: ←───┘ │@@: ←───┐ │ bra @b ────┘  なお、f は forward、b は backward の頭文字です。f や b は大文字で書い ても同じ意味になります。  「一番近くにあるものを参照する」と書きましたが、@@: の場合は 2 番目、3 番目と離れているものを参照することもできます。'@@f' は 2 つ後の '@@' を、 '@@b' は 2 つ前の '@@' を参照できます。 ┌──────────────────────────── │ bra @@f ────┐ │@@: ←───┼┐ │@@: ←───┘│ │ bra @@b ─────┘  '@@@f' や '@@@b' なども同様に 1 つずつ離れてゆきます。  255 個まで離れている @@: を参照できるようになっていますが、実際には 2 つ以上離れている @@: を参照することはほとんどありません。2 つ以上離れる ときは数字ローカルラベルを使ったほうが見やすいからです。 ●数字ローカルラベル  数字ローカルラベル '1' は、直前の '1f' または直後の '1b' から参照でき ます。'2'、'3'、…も同様です。  HAS.X では '1' から '9' まで、HAS060.X では '1' から '9999' まで使えま す。1 桁だと足りなくなることがありますが、普通は 2 桁もあれば足りると思 います。 GRAM(65536 色モード)を buffer にコピーする ┌──────────────────────────── │ lea.l $00C00000,a0 │ lea.l buffer,a1 │ move.w #512-1,d2 ←────┐ │2: move.w #512-1,d1 ←───┐│ │1: move.w (a0)+,(a1)+ ││ │ dbra d1,1b ────┘│ │ dbra d2,2b ─────┘ ~ │buffer: │ .dc.w 512*512  特定の数字ローカルラベルに意味を持たせておくと便利です。例えば、私はサ ブルーチンの終了位置に 99: を置くようにしています。S44PLAY.X のソースで も数字ローカルラベルを大量に使用しているので参考にして下さい。 ●ローカルラベルの実装方法  使い方とは関係ありませんが、ローカルラベルの機能の理解を助けるために、 HAS.X や HAS060.X における名無しのローカルラベルの実装方法を説明します。  HAS.X では 10 種類、HAS060.X では 10000 種類の名無しローカルラベルを使 えるようになっていますが、アセンブラの内部にそれぞれ今まで何回使われたか を数えているワークがあります。初期状態はすべて 0 回になっています。 @@: 0 回 1: 0 回 2: 0 回 :  この状態で例えば @@: というローカルラベルの定義が出てきたときは、それ が自動的に @@#0: というラベルに置き換えられると考えて下さい(実際に @@#0 という名前が付けられるわけではありませんが、意味としてはそういうことです)。 そして @@: の使用回数が 1 回増えて 1 回になります。同様に、2 番目に出て きた @@: は @@#1: になります。これが、同じ @@: というラベルを何度も定義 できる仕掛けです。要するに内部で順番に番号を振っているだけです。 ┌──────────────────────────── │@@: → @@#0: │@@: → @@#1:  さて、次に @f と @b がどうなっているのか説明しましょう。例えば @@: が 既に 2 回出てきているとき、ローカルラベルのカウンタは次のようになってい ます。 @@: 2 回 このとき、@f: は現在のカウンタの番号の @@: を参照します。つまり、@f とい うラベル参照は @@#2 というラベル参照に置き換えられるのです。@@#2: という ラベルは直後に出てきた @@: に対して割り当てられるので、@f は直後の @@: を参照することができるというわけです。同様に、@b は現在のカウンタの番号 から 1 を引いた @@: を参照します。つまり、今まで @@: が 2 回出てきた状態 で使用された @b は、@@#1: というラベル参照に置き換えられます。 ┌──────────────────────────── │@@: → @@#0: │@@: → @@#1: ←────┐ │ bra @f → @@#2: ────┐│ │ bra @b → @@#1: ────┼┘ │@@: → @@#2: ←───┘  まとめると、 : @@@b @@#(@@:の現在のカウンタ-3) を参照 @@b @@#(@@:の現在のカウンタ-2) を参照 @b @@#(@@:の現在のカウンタ-1) を参照 @f @@#(@@:の現在のカウンタ+0) を参照 @@f @@#(@@:の現在のカウンタ+1) を参照 @@@f @@#(@@:の現在のカウンタ+2) を参照 : ということになります。  数字ローカルラベルでも同様です。 1b 1#(1:の現在のカウンタ-1) を参照 1f 1#(1:の現在のカウンタ+0) を参照 2b 2#(2:の現在のカウンタ-1) を参照 2f 2#(2:の現在のカウンタ+0) を参照 : 9999b 9999#(9999:の現在のカウンタ-1) を参照 9999f 9999#(9999:の現在のカウンタ+0) を参照  HAS060.X は 10000 種類のローカルラベルを使用できるようになっているので、 カウンタのワークだけで 20KB 近く消費しています。
ラベルを自動的に外部定義にする
 ラベルに付ける ':' を 2 つ繋げて '::' と書くと、そのラベルが自動的に外 部定義になります。  ラベルを外部定義にしたいときは .xdef などを使って定義することもできま すが、'::' を使うと便利です。 ┌──────────────────────────── │ .xdef foo │foo: と ┌──────────────────────────── │foo:: は同じ意味です。
疑似命令の先頭に付ける '.' の意味
 アセンブラでは疑似命令を '.' で始めるという習慣があります。 ┌──────────────────────────── │ .text │ .even ~ などと書くわけです。  しかし、疑似命令に '.' を付けなければいけないという決まりがあるわけで はありません。逆に、普通の命令に '.' を付けてもエラーにはなりません。  命令や疑似命令の先頭に '.' を付けたときと付けないときでは、次のような 違いがあります。 命令や疑似命令と同名のマクロが定義されているとき、   '.' を付けたとき・・・・・・・命令や疑似命令を優先する   '.' を付けなかったたとき・・・マクロを優先する  例えば、次の例を見て下さい。 ┌──────────────────────────── │bra .macro lab │ bra.w lab │ .endm │ bra main ~ │main: ~ このプログラムでは bra 命令を強制的にワードサイズにするマクロを定義しよ うとしています。しかし、このままでは期待通りにアセンブルされません。マク ロの中の「bra.w lab」のところで再び bra マクロ(=自分自身)を呼び出して しまうので、「マクロのネストが深すぎます」というエラーが出てしまうのです。 そこで、マクロを次のように書き換えます。 ┌──────────────────────────── │bra .macro lab →│ .bra.w lab │ .endm │ bra main ~ │main: ~ 変更したのは、2 行目の bra.w の先頭に '.' を付けただけです。これだけでこ の bra.w はマクロよりも命令を優先して解釈されることになるので、自分自身 を再帰的に呼び出すことはなくなり、期待通りにアセンブルされます。  上記の例のような特別な場合以外は、「疑似命令には '.' を付け、命令とマ クロには '.' を付けない」という習慣に従いましょう。ただし、行頭のシンボ ルに定数を与える equ、set、=(set の別名)、reg、fequ、fset などには '.' を付けないほうが一般的です。
便利な疑似命令 reg
 マニュアルでは reg はシンボルにレジスタリストを割り付ける疑似命令とい うことになっていますが、実際にはレジスタリストに限らず、命令のオペランド に書くことができる文字列ならば何でもシンボルに割り付けることができます。  つまり、実は reg はシンボルに任意のオペランドを割り付ける疑似命令なの です。  一般的な使い方としては、サブルーチンでスタックにセーブするレジスタをシ ンボルに覚えさせておくときに使います。 ┌──────────────────────────── │save_regs reg d3-d7/a3-a6 │subroutine: │ movem.l save_regs,-(sp) ~ │ movem.l (sp)+,save_regs │ rts こうすると、「プログラムを書き換えているうちにプッシュするレジスタとポッ プするレジスタの数や種類が食い違ってしまった」などという間違いを減らせま す。  しかし、私がよく使う使い方はこれです。 ┌──────────────────────────── │PROGNAME reg 'S44PLAY' │VERSION reg '1.02' │DATE reg '2000.03.11' ~ │banner: │ .dc.b PROGNAME,'.X v',VERSION,' (',DATE,') by M.Kamada',0 ~ banner のところの文字列は「S44PLAY.X v1.02 (2000.03.11) by M.Kamada」と なります。  上の例では、reg の「オペランドなら何でも代入できる」という特徴を活かし て、シンボルに文字列を割り当てています。このようにプログラムの先頭でプロ グラムのバージョンや日付をシンボルに割り当てておくと、後で更新するときに いちいちプログラム中の文字列データを探して書き換えなくて済むので便利です。
くり返し その1(.rept~.endm)
 .rept はその名の通り、指定されたブロックを指定された回数だけ繰り返して アセンブルする疑似命令です。  例えば、テキスト VRAM に半角フォントを書き込むときは次のように書くこと ができます。 ┌──────────────────────────── │;<a0.l:テキストVRAMアドレス │;<a1.l:8×16ドットフォントアドレス │d = 0 │ .rept 16 │ move.b (a1)+,(d,a0) │d = d+128 │ .endm テキスト VRAM の Y 方向 1 ドットのオフセットは 128 なので、デスティネー ションのディスプレースメントを 128 ずつ増やしながら 16 回書き込んでいま す。  アセンブル時にコマンドラインに -p -f,1 を指定してアセンブルリストを出 力させてみると、.rept の挙動がよくわかります。 ┌──────────────────────────── ~ │<test.s> │ 1 00000000 ;<a0.l:テキストVRAMアドレス │ 2 00000000 ;<a1.l:8×16ドットフォントアドレス │ 3 00000000 =00000000 d = 0 │ 4 00000000 .rept 16 │ 5 00000000 move.b (a1)+,(d,a0) │ 6 00000000 d = d+128 │ 7 00000000 .endm │ 7 00000000*1099 move.b (a1)+,(d,a0) │ 7 00000002*=00000080 d = d+128 │ 7 00000002*11590080 move.b (a1)+,(d,a0) │ 7 00000006*=00000100 d = d+128 │ 7 00000006*11590100 move.b (a1)+,(d,a0) │ 7 0000000A*=00000180 d = d+128 │ 7 0000000A*11590180 move.b (a1)+,(d,a0) │ 7 0000000E*=00000200 d = d+128 │ 7 0000000E*11590200 move.b (a1)+,(d,a0) │ 7 00000012*=00000280 d = d+128 │ 7 00000012*11590280 move.b (a1)+,(d,a0) │ 7 00000016*=00000300 d = d+128 │ 7 00000016*11590300 move.b (a1)+,(d,a0) │ 7 0000001A*=00000380 d = d+128 │ 7 0000001A*11590380 move.b (a1)+,(d,a0) │ 7 0000001E*=00000400 d = d+128 │ 7 0000001E*11590400 move.b (a1)+,(d,a0) │ 7 00000022*=00000480 d = d+128 │ 7 00000022*11590480 move.b (a1)+,(d,a0) │ 7 00000026*=00000500 d = d+128 │ 7 00000026*11590500 move.b (a1)+,(d,a0) │ 7 0000002A*=00000580 d = d+128 │ 7 0000002A*11590580 move.b (a1)+,(d,a0) │ 7 0000002E*=00000600 d = d+128 │ 7 0000002E*11590600 move.b (a1)+,(d,a0) │ 7 00000032*=00000680 d = d+128 │ 7 00000032*11590680 move.b (a1)+,(d,a0) │ 7 00000036*=00000700 d = d+128 │ 7 00000036*11590700 move.b (a1)+,(d,a0) │ 7 0000003A*=00000780 d = d+128 │ 7 0000003A*11590780 move.b (a1)+,(d,a0) │ 7 0000003E*=00000800 d = d+128 ~ 1 回目は d = 0 なので (d,a0) が (a0) に変換されることにも注目して下さい。 ● .rept~.endm の実装方法  .rept~.endm は名無しマクロの一種です。.rept からマクロの定義が開始さ れ、.endm が出てきたら自動的にそこまでのコードを .rept で指定された回数 だけ繰り返して展開しているだけなのです。  .rept の内部で発生したアセンブルエラーの多くが .endm の行で通知される のは、マクロの内部で発生したアセンブルエラーがほとんどマクロの定義のとき ではなくてマクロの展開のときに発生するのと同じことです。
くり返し その2(.irp~.endm)
 .irp もループ展開を行う疑似命令です。.rept と違い、.irp では 1 回展開 する毎に特定のシンボルに指定された文字列を割り当てることができます。1 つ の引数を持つ名無しマクロに毎回異なる引数を与えて展開する、と言ったほうが わかりやすいかも知れません。  .irp の最初の引数はその .irp~.endm の中だけで有効なシンボルです。その シンボルに 2 番目以降の引数が順に代入されながら、.irp~.endm の中のコー ドが繰り返し展開されます。 ┌──────────────────────────── │ .irp rgb,red,green,blue │str_&rgb: .dc.b '&rgb',0 │ .endm この場合は rgb というシンボルに red、green、blue が順に代入されます。展 開結果は、 ┌──────────────────────────── │ 3 00000000*72656400 str_red: .dc.b 'red',0 │ 3 00000004*677265656E00 str_green: .dc.b 'green',0 │ 3 0000000A*626C756500 str_blue: .dc.b 'blue',0  .irp~.endm の中では、マクロの場合と同様に &シンボル の形式で引数を参 照できます。シンボルが独立しているときは '&' は無くても構いません。
くり返し その3(.irpc~.endm)
 .irpc は .irp の変化形です。.irp が 2 番目以降の引数を順にシンボルに割 り当ててゆくのに対して、.irpc は 2 番目の引数の文字を 1 文字ずつ順にシン ボルに割り当ててゆきます。 ┌──────────────────────────── │ea_jump_table: │ .irpc rrr,01234567 │ .irpc mmm,01234567 │ .dc.w ea_&rrr&&mmm-ea_jump_table │ .endm │ .endm .irpc を二重に使っているので、外側のループの引数の rrr の参照は &rrr で、 内側のループの引数の mmm の参照が &&mmm になっていることに注目して下さい。 外側のループが展開された時点で、&&mmm が &mmm に変化し、さらに内側のルー プが展開されるときに &mmm が文字に置き換わります。展開結果は、 ┌──────────────────────────── │ 1 00000000 ea_jump_table: │ 2 00000000 .irpc rrr,01234567 │ 3 00000000 .irpc mmm,01234567 │ 4 00000000 .dc.w ea_&rrr&&mmm-ea_jump_table │ 5 00000000 .endm │ 6 00000000 .endm │ 6 00000000* .irpc mmm,01234567 │ 6 00000000* .dc.w ea_0&mmm-ea_jump_table │ 6 00000000* .endm │ 6 00000000*???? .dc.w ea_00-ea_jump_table │ 6 00000002*???? .dc.w ea_01-ea_jump_table │ 6 00000004*???? .dc.w ea_02-ea_jump_table │ 6 00000006*???? .dc.w ea_03-ea_jump_table │ 6 00000008*???? .dc.w ea_04-ea_jump_table │ 6 0000000A*???? .dc.w ea_05-ea_jump_table │ 6 0000000C*???? .dc.w ea_06-ea_jump_table │ 6 0000000E*???? .dc.w ea_07-ea_jump_table │ 6 00000010* .irpc mmm,01234567 │ 6 00000010* .dc.w ea_1&mmm-ea_jump_table │ 6 00000010* .endm │ 6 00000010*???? .dc.w ea_10-ea_jump_table │ 6 00000012*???? .dc.w ea_11-ea_jump_table │ 6 00000014*???? .dc.w ea_12-ea_jump_table │ 6 00000016*???? .dc.w ea_13-ea_jump_table ~ │ 6 00000070* .irpc mmm,01234567 │ 6 00000070* .dc.w ea_7&mmm-ea_jump_table │ 6 00000070* .endm │ 6 00000070*???? .dc.w ea_70-ea_jump_table │ 6 00000072*???? .dc.w ea_71-ea_jump_table │ 6 00000074*???? .dc.w ea_72-ea_jump_table │ 6 00000076*???? .dc.w ea_73-ea_jump_table │ 6 00000078*???? .dc.w ea_74-ea_jump_table │ 6 0000007A*???? .dc.w ea_75-ea_jump_table │ 6 0000007C*???? .dc.w ea_76-ea_jump_table │ 6 0000007E*???? .dc.w ea_77-ea_jump_table ここでは ea_00 などのシンボルが未定義なので値は ???? になっています。  .irpc の 2 番目の引数が '~' で囲まれているときは、'~' の中の文字列だ けが文字を取り出す対象になります。  こんなこともできます。 ┌──────────────────────────── │foo .macro str │ .irpc c,str │ .if ('a'<='&c').and.('&c'<='z') │ .dc.b '&c'.and.$df │ .else │ .dc.b '&c' │ .endif │ .endm │ .endm │ foo 'm68k' 何をやっているか、だいたい予想がつくでしょうか? ┌──────────────────────────── │<test.s> │ 1 00000000 foo .macro str │ 2 00000000 .irpc c,str │ 3 00000000 .if ('a'<='&c').and.('&c'<='z') │ 4 00000000 .dc.b '&c'.and.$df │ 5 00000000 .else │ 6 00000000 .dc.b '&c' │ 7 00000000 .endif │ 8 00000000 .endm │ 9 00000000 .endm │ 10 00000000 foo 'm68k' │ 10 00000000* .irpc c,'m68k' │ 10 00000000* .if ('a'<='&c').and.('&c'<='z') │ 10 00000000* .dc.b '&c'.and.$df │ 10 00000000* .else │ 10 00000000* .dc.b '&c' │ 10 00000000* .endif │ 10 00000000* .endm │ 10 00000000* .if ('a'<='m').and.('m'<='z') │ 10 00000000*4D .dc.b 'm'.and.$df │ 10 00000001* .else │ 10 00000001* .dc.b 'm' │ 10 00000001* .endif │ 10 00000001* .if ('a'<='6').and.('6'<='z') │ 10 00000001* .dc.b '6'.and.$df │ 10 00000001* .else │ 10 00000001*36 .dc.b '6' │ 10 00000002* .endif │ 10 00000002* .if ('a'<='8').and.('8'<='z') │ 10 00000002* .dc.b '8'.and.$df │ 10 00000002* .else │ 10 00000002*38 .dc.b '8' │ 10 00000003* .endif │ 10 00000003* .if ('a'<='k').and.('k'<='z') │ 10 00000003*4B .dc.b 'k'.and.$df │ 10 00000004* .else │ 10 00000004* .dc.b 'k' │ 10 00000004* .endif 「指定された文字列を 1 文字ずつ分解して、それぞれの文字が 'a'~'z' の範 囲内なら $df と .and. したものを、そうでなければその文字をそのまま出力す る」、つまり、「文字列を大文字化して出力するマクロ」です。
マクロ内ローカルラベル
 .macro~.endm で定義されたマクロの中では、疑似命令 .local を使ってその マクロの中だけで有効なラベルを定義することができます。  例えば、指定された回数だけ指定されたサブルーチンを呼び出すマクロは次の ように定義できます。 ┌──────────────────────────── │repeat .macro n,lab │ .local loop │ move.w #n,-(sp) │loop: │ jsr lab │ subq.w #1,(sp) │ bne loop │ addq.l #2,sp │ .endm ~ │ repeat 10,subroutine  さて、マクロ内ローカルラベルの数が多くなってくると、いちいち .local で ラベルを宣言するのは面倒です。そこで、HAS060.X にはマクロ内ローカルラベ ルが自動的に宣言されるという便利な機能が追加されています。マクロの中では '@' で始まるラベルを使用すると、勝手にマクロ内ローカルラベルになるのです。 ただし、@f、@F、@b、@B および @@~ で始まるラベルはマクロ内ローカルラベ ルとしては使えません。 ┌──────────────────────────── │repeat .macro n,lab │ move.w #n,-(sp) │@loop: │ jsr lab │ subq.w #1,(sp) │ bne @loop │ addq.l #2,sp │ .endm ~ │ repeat 10,subroutine  マクロ内ローカルシンボルを大量に使用しているプログラムの例としては、 S44PLAY.X の core.s などがあります。
オペレーションサイズを解釈するマクロ
 命令に近い形のマクロを定義することができるようにするために、HAS060.X ではマクロの使用時に指定されたオペレーションサイズをマクロの中で使用する ことができるようになっています。  マクロ定義の先頭で .sizem を使ってシンボルを宣言することで、マクロに指 定されたオペレーションサイズを .sizem で指定したシンボルで受け取ることが できます。 ┌──────────────────────────── │inc .macro ea │ .sizem sz │ addq&sz #1,ea │ .endm │ inc.l d0 │ inc (a0)+ 展開結果は、 ┌──────────────────────────── │ 5 00000000 inc.l d0 │ 5 00000000*5280 addq.l #1,d0 │ 6 00000002 inc (a0)+ │ 6 00000002*5258 addq #1,(a0)+  なお、.sizem はマクロに指定された引数の個数を受け取るシンボルを宣言す ることもできます。詳しくは HAS060.X のマニュアルを参照して下さい。
cc と Ncc
 ここで言う cc というのは、Bcc や DBcc と書くときの cc、つまり、ニモニ ックの中のコンディションコードを表す部分のことです。HAS060.X には、cc の 代わりに Ncc を指定することで条件を逆にする機能があります。 ┌──────────────────────────── │ .irp cc,hi,ls,cc,hs,cs,lo,ne,eq,vc,vs,pl,mi,ge,lt,gt,le │if&cc .macro cmd │ bn&cc @skip │ cmd │@skip: │ .endm │ .endm │ tst.l d0 │ ifmi <neg.l d0> .irp~.endm の中に .macro~.endm があることにも注目。これで一度に大量の マクロを定義しています。-p -f,1 でアセンブルしてみると、以下のようなアセ ンブルリストが得られます。 ┌──────────────────────────── │<test.s> │ 1 00000000 .irp cc,hi,ls,cc,hs,cs,lo,ne,eq,vc,vs,pl,mi,ge,lt,gt,le │ 2 00000000 if&cc .macro cmd │ 3 00000000 bn&cc @skip │ 4 00000000 cmd │ 5 00000000 @skip: │ 6 00000000 .endm │ 7 00000000 .endm │ 7 00000000* ifhi .macro cmd │ 7 00000000* bnhi @skip │ 7 00000000* cmd │ 7 00000000* @skip: │ 7 00000000* .endm │ 7 00000000* ifls .macro cmd │ 7 00000000* bnls @skip │ 7 00000000* cmd │ 7 00000000* @skip: │ 7 00000000* .endm │ 7 00000000* ifcc .macro cmd │ 7 00000000* bncc @skip │ 7 00000000* cmd │ 7 00000000* @skip: │ 7 00000000* .endm │ 7 00000000* ifhs .macro cmd │ 7 00000000* bnhs @skip │ 7 00000000* cmd │ 7 00000000* @skip: │ 7 00000000* .endm │ 7 00000000* ifcs .macro cmd │ 7 00000000* bncs @skip │ 7 00000000* cmd │ 7 00000000* @skip: │ 7 00000000* .endm │ 7 00000000* iflo .macro cmd │ 7 00000000* bnlo @skip │ 7 00000000* cmd │ 7 00000000* @skip: │ 7 00000000* .endm │ 7 00000000* ifne .macro cmd │ 7 00000000* bnne @skip │ 7 00000000* cmd │ 7 00000000* @skip: │ 7 00000000* .endm │ 7 00000000* ifeq .macro cmd │ 7 00000000* bneq @skip │ 7 00000000* cmd │ 7 00000000* @skip: │ 7 00000000* .endm │ 7 00000000* ifvc .macro cmd │ 7 00000000* bnvc @skip │ 7 00000000* cmd │ 7 00000000* @skip: │ 7 00000000* .endm │ 7 00000000* ifvs .macro cmd │ 7 00000000* bnvs @skip │ 7 00000000* cmd │ 7 00000000* @skip: │ 7 00000000* .endm │ 7 00000000* ifpl .macro cmd │ 7 00000000* bnpl @skip │ 7 00000000* cmd │ 7 00000000* @skip: │ 7 00000000* .endm │ 7 00000000* ifmi .macro cmd │ 7 00000000* bnmi @skip │ 7 00000000* cmd │ 7 00000000* @skip: │ 7 00000000* .endm │ 7 00000000* ifge .macro cmd │ 7 00000000* bnge @skip │ 7 00000000* cmd │ 7 00000000* @skip: │ 7 00000000* .endm │ 7 00000000* iflt .macro cmd │ 7 00000000* bnlt @skip │ 7 00000000* cmd │ 7 00000000* @skip: │ 7 00000000* .endm │ 7 00000000* ifgt .macro cmd │ 7 00000000* bngt @skip │ 7 00000000* cmd │ 7 00000000* @skip: │ 7 00000000* .endm │ 7 00000000* ifle .macro cmd │ 7 00000000* bnle @skip │ 7 00000000* cmd │ 7 00000000* @skip: │ 7 00000000* .endm │ 8 00000000 4A80 tst.l d0 │ 9 00000002 ifmi <neg.l d0> │ 9 00000002*6A02_00000006 bnmi ??0001 │ 9 00000004*4480 neg.l d0 │ 9 00000006* ??0001:  使用頻度は高くありませんが、Ncc はマクロで機械的に条件を入れ替えたいと きに便利です。HAS060.X に添付されている K_MACRO.MAC でも大量に使用してい ますので、参考にしてみて下さい。
JBRA、JBSR、JBcc
 68000 では、bra 命令は bra.s と bra.w しか使えません。そのため、コード が 32KB を越えるプログラムを書くとき、bra ではオフセットが届かなくなって しまうことがあります。「jmp を使うとコードが長くなってしまう(3 ワード必 要)のでなるべく bra(2 ワード)を使いたいけれど、どの分岐が bra で届く かわからない」というときは、jbra を使うと便利です。  jbra という命令は m68k の命令としては実在しません。アセンブラがオフセ ットに応じて bra か jmp に自動的に読み代えます。jbsr も同様です。jbcc は、 bcc でオフセットが届かなければ、逆条件の bncc 命令で jsr や jmp 命令を飛 び越えるような 4 ワードの命令列が自動的に生成されます。 ┌──────────────────────────── │ jbhi foo │ .rept 20000 │ nop │ .endm │foo: これを展開すると、 ┌──────────────────────────── │ 1 00000000 63064EF9(01)0000 jbhi foo │ 9C48 │ 2 00000008 .rept 20000 │ 3 00000008 nop │ 4 00000008 .endm │ 5 00009C48 foo: となります。jbhi が 4 ワードの命令列に展開されていることがわかります。こ の 4 ワードを逆アセンブルしてみるとわかりますが、jbhi の部分の 4 ワード の命令は、次のようなコードになっています。 ┌──────────────────────────── │ bls.s L000006 │ jmp foo │L000006: bls のように分岐の条件が ls になっているのは、jbhi の hi の逆条件である nhi というのが ls と等価だからです。この命令列で、「hi ならば jmp する」 という jbhi の機能を実現しているというわけです。
バイナリデータを埋め込む
 アセンブラのソースの中にスプライトデータなどのバイナリデータを埋め込み たいときがあります。16 進数でダンプして .dc.b や $ を付けて整形してもよ いのですが、面倒なので、HAS060.X ではバイナリデータの入っているファイル を直接プログラムに埋め込むことができるようにしました。  疑似命令 .insert は指定されたファイルをバイナリデータと見なしてプログ ラムに埋め込みます。埋め込むデータの範囲は指定できますが、データそのもの は一切加工されません。  電脳倶楽部 142 号に掲載した『ごーじゃすリバーシ』のソース(graph.s)で .insert を有効に利用しているので引用します。 ┌──────────────────────────── │ .irp moji,0,1,2,3,4,5,6,7,8,9 │spc&moji: .insert sp/&moji.sp │ .endm │ .irp moji,き,け,ち,の,ス,パ,ベ,ル,レ,引,局,後,黒,手,終,勝,石,先,対,白,分,了 │spc&moji: .insert sp/&moji.sp │ .endm アセンブルリストを吐かせると、挿入されたバイナリデータがすべて出てきます。 ┌──────────────────────────── │ 1 00000000 .irp moji,0,1,2,3,4,5,6,7,8,9 │ 2 00000000 spc&moji: .insert sp/&moji.sp │ 3 00000000 .endm │ 3 00000000*0000000000000000 spc0: .insert sp/0.sp │ 0000000000000000 │ 0000000000000000 │ 0000000000000003 │ 0000000A0000001F │ 0000005F0000008F │ 000000AF000000CF │ 000000DF000000FF ~ │ 3 00000200*0000000000000000 spc1: .insert sp/1.sp ~ │ 4 00001400 .irp moji,き,け,ち,の,ス,パ,ベ,ル,レ,引,局,後,黒,手,終,勝,石,先,対,白,分,了 │ 5 00001400 spc&moji: .insert sp/&moji.sp │ 6 00001400 .endm │ 6 00001400*0000000000000000 spcき: .insert sp/き.sp │ 0000000000000000 │ 0000000000000000 │ 0000000000004444 │ 00008FFF00008FFF │ 0000488600000000 │ 00000000008ECCCC │ 008FFFFF008FFFFF ~ │ 6 00003E00*0000000000000000 spc了: .insert sp/了.sp ~
.not. と .notb. と .notw.
 .not. は単項演算子です。もっと詳しく言うと、ビット毎の論理否定演算子と いうやつです。ですから、 .not.$55555555 = $AAAAAAAA .not.$AAAAAAAA = $55555555 です。  では、次のソースをアセンブルしたらどうなるでしょう? ┌──────────────────────────── │ .dc.b .not.$55 │ .dc.b .not.$AA 答えは「1 行目が $AA で 2 行目が $55 になる」ですか? 残念ながらハズレ です。アセンブルしてみるとわかりますが、2 行目だけエラーになります。 ┌──────────────────────────── │<test.s> │ 1 00000000 AA .dc.b .not.$55 │test.s 2: Error: オーバーフローしました │ 2 00000001 .dc.b .not.$AA  理由は簡単、 .not.$55 = $FFFFFFAA .not.$AA = $FFFFFF55 で、2 番目の $FFFFFF55 = -171 は .dc.b の引数に許される「符号なしまたは 符号あり 8 ビット整数(具体的には -128~255)」の範囲外ということでオー バーフローエラーになります。  しかし、この少々分かり難い問題に遭遇してしまい、「アセンブラのバグでは ないか?」と“バグ報告”を寄せて下さった方が何人かいらっしゃったのです。 報告を寄せて下さった方には何故そうなるのかをきちんと説明したのですが、い ちいち「(.not.$AA).and.$FF」または「$AA.xor.$FF」と書いてもらうのも面倒 だろうと思ったので、専用の演算子を作ってしまいました。  HAS060.X のマニュアルからそのまま引用してしまいますが、.notb. と .notw. の定義は次のようになっています。 .NOTB.<式> ::= (.NOT.<式>).AND.$FF .NOTW.<式> ::= (.NOT.<式>).AND.$FFFF つまり、 .notb.$55 = $000000AA .notb.$AA = $00000055 .notw.$5555 = $0000AAAA .notw.$AAAA = $00005555 という具合です。先ほどのソースも、 ┌──────────────────────────── │ .dc.b .notb.$55 │ .dc.b .notb.$AA と書けば、 ┌──────────────────────────── │<test.s> │ 1 00000000 AA .dc.b .notb.$55 │ 2 00000001 55 .dc.b .notb.$AA となって、これで問題解決です。
 思いついたところをいろいろ挙げてみましたが、いかがでしたでたか?  「こんな使い方があったのか!」という発見がありましたか? (EOF)